﻿using DBUtil;
using DotNetCommon.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Jackletter.BlendAuth
{
    public class TokenManager : ITokenManager
    {
        public DBAccess db;
        private ConcurrentDictionary<string, BlendAuthModel> _caches = new ConcurrentDictionary<string, BlendAuthModel>();
        private FreeRedis.RedisClient _redis;

        private readonly BlockingCollection<DBOpType> queue = new BlockingCollection<DBOpType>();
        private readonly ILogger<TokenManager> logger;

        public TokenManager(ILogger<TokenManager> logger, BlendAuthenticationOptions options)
        {
            this.logger = logger;
            this.options = options;
            logger.LogTrace($"tokenManager实例已创建,即将进行初始化...");
            Init();
            logger.LogTrace($"tokenManager实例初始化已完成");
        }

        public virtual void EnsureTable()
        {
            if (!db.JudgeTableExist("__blend_auth"))
            {
                logger.LogTrace($"检测到表__blend_auth尚未创建,即将进行初始化建表...");
                db.ExecuteSql(@"
                create table __blend_auth(
                    auth_token varchar(200) primary key,
                    auth_userid bigint,
                    auth_system varchar(200),
                    auth_client varchar(200),
                    auth_login_time datetime,
                    auth_lastrefresh_time datetime,
                    auth_slidingwindowstart_time datetime,
                    auth_slidingwindowend_time datetime
                )");
                logger.LogTrace($"表__blend_auth已创建完成");
            }
        }


        private bool _isInit = false;
        public BlendAuthenticationOptions Options { get => options; }
        private BlendAuthenticationOptions options;

        private void Init()
        {
            if (!_isInit)
            {
                lock (typeof(TokenManager))
                {
                    if (!_isInit)
                    {
                        logger.LogTrace($"tokenManager实例尚未进行初始化,即将进行初始化...");
                        logger.LogTrace($"检测到token的持久化数据库配置为: [{options.DBType}]-[{options.DBConn}]");
                        db = DBFactory.CreateDB(options.DBType, options.DBConn);
                        logger.LogTrace($"已为tokenManager实例准备好数据库");
                        if (!string.IsNullOrWhiteSpace(options.RedisConn))
                        {
                            logger.LogTrace($"检测到token的同步redis配置为: [{options.RedisConn}]");
                            _redis = new FreeRedis.RedisClient(options.RedisConn);
                            logger.LogTrace($"已为tokenManager实例准备redis同步对象");
                        }
                        InitRedisSync();
                        InitCaches();
                        _isInit = true;
                        logger.LogTrace($"tokenManager实例已初始化完毕");
                    }
                }
            }
        }

        private void InitRedisSync()
        {
            //同步登录请求
            _redis?.Subscribe("__blend_auth:login", (key, obj) =>
            {
                try
                {
                    logger.LogTrace($"接收到同步请求,类型: 登录,内容: {obj?.ToString()}");
                    var blend = Newtonsoft.Json.JsonConvert.DeserializeObject<BlendAuthModel>(obj?.ToString());
                    _caches.AddOrUpdate(blend.auth_token, blend, (key2, model) =>
                     {
                         return blend;
                     });
                    logger.LogTrace($"已尝试将登录的请求同步到内存字典,token={key}");
                }
                catch (Exception ex)
                {
                    logger.LogError($"同步登录请求出错:{ex?.Message}");
                }
            });
            //同步注销请求
            _redis?.Subscribe("__blend_auth:logout", (key, obj) =>
            {
                try
                {
                    logger.LogTrace($"接收到同步请求,类型: 注销,内容: {obj?.ToString()}");
                    var token = obj?.ToString();
                    _caches.TryRemove(token, out _);
                    logger.LogTrace($"已尝试从内存字典中移除token,token={key}");
                }
                catch (Exception ex)
                {
                    logger.LogError($"同步注销请求出错:{ex?.Message}");
                }
            });
            //同步刷新滑动窗口请求
            _redis?.Subscribe("__blend_auth:refresh", (key, obj) =>
            {
                try
                {
                    logger.LogTrace($"接收到同步请求,类型: 刷新,内容: {obj?.ToString()}");
                    var model = Newtonsoft.Json.JsonConvert.DeserializeObject<BlendAuthModel>(obj?.ToString());
                    _caches.AddOrUpdate(model.auth_token, model, (key2, old) => model);
                    logger.LogTrace($"已尝试更新内存字典中的token,token={key}");
                }
                catch (Exception ex)
                {
                    logger.LogError($"同步刷新请求出错:{ex?.Message}");
                }
            });
        }

        private void DeleteExpire()
        {
            var now = DateTime.Now;
            var filter = db.Expression_DateTime.GetFilterString("auth_slidingwindowend_time", null, now, false, false);
            logger.LogTrace($"即将执行删除过期token的sql语句:{filter}");
            var res = db.Delete("__blend_auth", filter);
            logger.LogTrace($"已执行删除过期token的sql语句[删除了{res}条数据]:{filter}");

        }

        private void InitCaches()
        {
            EnsureTable();
            DeleteExpire();
            logger.LogTrace($"即将从数据库中初始化token数据...");
            var models = db.SelectModels<BlendAuthModel>("select * from __blend_auth");
            logger.LogTrace($"从数据库中获取到了{models.Count}条token数据");
            models.ForEach(m => _caches.TryAdd(m.auth_token, m));
            logger.LogTrace($"已尝试将数据库中获取到的token数据存放到内存字典中.");
            //开启线程监听操作队列
            Task.Factory.StartNew(() =>
            {
                DBOpType op = null;
                while (true)
                {
                    try
                    {
                        op = queue.Take();
                        logger.LogTrace($"从数据库队列中监听到操作:{op.OpType}");
                        if (op.OpType == EnumDBOpType.AddUser)
                        {
                            BlendAuthModel entry = op.OpParas as BlendAuthModel;
                            logger.LogTrace($"即将执行添加新用户到数据库的操作,token={entry?.auth_token}");
                            var res = db.Insert("__blend_auth", entry.ToDictionary());
                            logger.LogTrace($"已将新用户添加到数据库[,token={entry?.auth_token}");
                        }
                        else if (op.OpType == EnumDBOpType.DeleteExpires)
                        {
                            DeleteExpire();
                        }
                        else if (op.OpType == EnumDBOpType.DeleteUser)
                        {
                            string token = op.OpParas as string;
                            db.Delete("__blend_auth", new { auth_token = token }.ToDictionary());
                        }
                        else if (op.OpType == EnumDBOpType.DeleteUsersBatch)
                        {
                            List<string> tokens = op.OpParas as List<string>;
                            if (tokens.Count > 0)
                            {
                                string filter = "";
                                tokens.ForEach(i =>
                                {
                                    filter += string.Format("'{0}',", i);
                                });
                                filter = filter.TrimEnd(',');
                                var filterSql = $" and auth_token in ({filter})";
                                logger.LogTrace($"即将执行批量删除数据库用户的操作,filter={filterSql}");
                                var res = db.Delete("__blend_auth", filterSql);
                                logger.LogTrace($"已执行批量删除数据库用户的操作[删除了{res}条用户认证信息],filter={filterSql}");
                            }
                        }
                        else if (op.OpType == EnumDBOpType.RefreshUser)
                        {
                            var entry = op.OpParas as BlendAuthModel;
                            logger.LogTrace($"即将执行刷新数据库用户的操作,token={entry.auth_token}");
                            db.Update("__blend_auth", new
                            {
                                auth_lastrefresh_time = entry.auth_lastrefresh_time,
                                auth_slidingwindowstart_time = entry.auth_slidingwindowstart_time,
                                auth_slidingwindowend_time = entry.auth_slidingwindowend_time
                            }.ToDictionary(), new
                            {
                                auth_token = entry.auth_token
                            }.ToDictionary());
                            logger.LogTrace($"已执行刷新数据库用户的操作,token={entry.auth_token}");
                        }
                    }
                    catch (Exception ex)
                    {
                        logger.LogError(ex, "执行DBOpType出错:" + op.ToString());
                    }
                }
            });
            logger.LogTrace($"已开启数据库操作队列监听");
        }

        public virtual string SignIn(long userId, SignOptions options)
        {
            if (options == null) options = new SignOptions();
            if (options.SlidingWindow == null) options.SlidingWindow = Options.SlidingTimeSpan;

            string token = Guid.NewGuid().ToString().Replace("-", "") + Guid.NewGuid().ToString().Replace("-", "");
            token = token.ToUpper();
            var now = DateTime.Now;
            var entry = new BlendAuthModel();
            entry.auth_login_time = now;
            entry.auth_userid = userId;
            entry.auth_system = options.SystemCode;
            entry.auth_client = options.ClientCode;
            entry.auth_token = token;
            entry.auth_slidingwindowstart_time = now;
            entry.auth_slidingwindowend_time = now + options.SlidingWindow.Value;
            entry.auth_lastrefresh_time = now;
            AddUser(token, entry);
            return token;
        }

        public virtual void SignOut(string token)
        {
            RemoveToken(token);
        }

        public virtual void SignOut(long UserId)
        {
            RemoveToken(UserId);
        }

        public void RemoveToken(string token)
        {
            _caches.TryRemove(token, out BlendAuthModel entry);
            queue.Add(new DBOpType()
            {
                OpType = EnumDBOpType.DeleteUser,
                OpParas = token
            });
            _redis?.Publish("__blend_auth:logout", token);
        }

        public void RemoveToken(long userId)
        {
            var entry = _caches.Values.FirstOrDefault(i => i.auth_userid == userId);
            if (entry != null)
            {
                RemoveToken(entry.auth_token);
            }
        }
        public void RefreshUser(BlendAuthModel entry)
        {
            var now = DateTime.Now;
            entry.auth_lastrefresh_time = now;
            entry.auth_slidingwindowend_time = now + (entry.auth_slidingwindowend_time - entry.auth_slidingwindowstart_time);
            entry.auth_slidingwindowstart_time = now;
            queue.Add(new DBOpType()
            {
                OpType = EnumDBOpType.RefreshUser,
                OpParas = entry
            });
            _redis?.Publish("__blend_auth:refresh", Newtonsoft.Json.JsonConvert.SerializeObject(entry));
        }

        private void AddUser(string token, BlendAuthModel entry)
        {
            _caches.TryAdd(token, entry);

            queue.Add(new DBOpType()
            {
                OpType = EnumDBOpType.AddUser,
                OpParas = entry
            });
            _redis?.Publish("__blend_auth:login", Newtonsoft.Json.JsonConvert.SerializeObject(entry));
            StartScanExpire();
        }

        private DateTimeOffset LastScanTime = DateTimeOffset.MinValue;
        private void StartScanExpire()
        {
            var now = DateTimeOffset.Now;
            if (LastScanTime == null || (now - LastScanTime) > options.CheckExpireInterval)
            {
                lock (typeof(TokenManager))
                {
                    if (LastScanTime == null || (now - LastScanTime) > options.CheckExpireInterval)
                    {
                        StartScanExpireReal();
                        LastScanTime = now;
                    }
                }
            }
        }
        private void StartScanExpireReal()
        {
            var list = _caches.Values.ToList();
            List<string> expireTokens = new List<string>();
            var now = DateTimeOffset.Now;
            list.ForEach(i =>
            {
                if (i.auth_slidingwindowend_time < now)
                {
                    expireTokens.Add(i.auth_token);
                }
            });
            if (expireTokens.Count > 0)
            {
                expireTokens.ForEach(i =>
                {
                    _caches.TryRemove(i, out BlendAuthModel entry);
                });
                queue.Add(new DBOpType()
                {
                    OpType = EnumDBOpType.DeleteUsersBatch,
                    OpParas = expireTokens
                });
            }
        }


        public virtual BlendUser GetUser(string token)
        {
            if (token == null) return null;
            if (_caches.TryGetValue(token, out BlendAuthModel entry))
            {
                var now = DateTime.Now;
                if (now >= entry.auth_slidingwindowstart_time && now <= entry.auth_slidingwindowend_time)
                {
                    //在滑动窗口内
                    if (now - entry.auth_lastrefresh_time >= Options.SlidingTriggerInterval)
                    {
                        //刷新token
                        RefreshUser(entry);
                    }
                    return new BlendUser() { UserId = entry.auth_userid, Token = entry.auth_token, SystemCode = entry.auth_system, ClientCode = entry.auth_client };
                }
                else
                {
                    //不在滑动窗口内 移除token
                    RemoveToken(token);
                }
            }
            return null;
        }
    }

    internal class DBOpType
    {
        public EnumDBOpType OpType { set; get; }
        public Object OpParas { set; get; }
        public override string ToString()
        {
            return Newtonsoft.Json.JsonConvert.SerializeObject(this);
        }
    }

    internal enum EnumDBOpType
    {
        RefreshUser, AddUser, DeleteUser, DeleteExpires, DeleteUsersBatch
    }
}
